GuardDutyの検出結果をエクスポートするためのKMSキーとS3バケットをCloudFormationで作った
コンバンハ、千葉(幸)です。
GuardDutyでは検出結果をS3にエクスポートすることができます。
エクスポート時には、内容を暗号化するためのKMSキーと、必要なバケットポリシーを定義したS3バケットを準備する必要があります。それらをサクッと用意するためのCloudFormationのテンプレートを作成しました。
目次
- 目次
- 本記事の対象範囲
- GuardDutyの検出結果のS3へのエクスポート
- GuardDutyを有効化するCloudFormationテンプレート
- 追加するCloudFormationテンプレート
- S3へのエクスポート設定
- 終わりに
本記事の対象範囲
以下のイメージです。
青枠で囲っている部分が、今回新たに作成したCloudFormationテンプレートで構成されるリソースです。
補足
- エクスポート用のKMSキーおよびS3バケットはGuardDutyを有効化するリージョンと同一のリージョンで新規作成するものとしています
- GuardDutyディテクター、CloudWatchイベントルール、SNSトピックなどのリソースは、先人のテンプレートがあるのでそれを活用させてもらいます
- S3へのエクスポートも含めてCloudFormationで完結できるのでは、と考えていたのですが、2020年3月時点でリファレンスに記述が見当たらなかったので、そこの設定だけは手動で行うものとしています
GuardDutyの検出結果のS3へのエクスポート
詳細は以下の記事に詳しいです。
公式ドキュメントも一緒に確認しておきましょう。
簡単に言うと、以下の事前設定が必要です。
- KMSキー(カスタマーマスターキー)
- S3バケットポリシー
エクスポートの際にはカスタマーマスタキーによる暗号化が必須になっています。暗号化なしや、デフォルトKMSキーを用いた暗号化には対応していません。
また、ここでのKMSキーとS3バケットは同一のリージョンにある必要があります。
KMSキーポリシー
使用するKMSキーはキーポリシーによって以下が許可されている必要があります。
{ "Sid": "Allow GuardDuty to use the key", "Effect": "Allow", "Principal": { "Service": "guardduty.amazonaws.com" }, "Action": "kms:GenerateDataKey", "Resource": "*" }
S3バケットポリシー
エクスポート先のS3バケットはバケットポリシーによってGuardDutyからのエクスポートを許可していることが必要です。
ドキュメントに記載されているサンプルは以下の通りです。Deny
の部分は必須ではありませんが、特に理由がなければ制限しておくに越したことはないでしょう。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "Allow GuardDuty to use the getBucketLocation operation", "Effect": "Allow", "Principal": { "Service": "guardduty.amazonaws.com" }, "Action": "s3:GetBucketLocation", "Resource": "arn:aws:s3:::myBucketName" }, { "Sid": "Allow GuardDuty to upload objects to the bucket", "Effect": "Allow", "Principal": { "Service": "guardduty.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::myBucketName/[optional prefix]/*" }, { "Sid": "Deny unencrypted object uploads. This is optional", "Effect": "Deny", "Principal": { "Service": "guardduty.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::myBucketName/[optional prefix]/*", "Condition": { "StringNotEquals": { "s3:x-amz-server-side-encryption": "aws:kms" } } }, { "Sid": "Deny incorrect encryption header. This is optional", "Effect": "Deny", "Principal": { "Service": "guardduty.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::myBucketName/[optional prefix]/*", "Condition": { "StringNotEquals": { "s3:x-amz-server-side-encryption-aws-kms-key-id": "arn:aws:kms:region:111122223333:key/KMSKeyId" } } }, { "Sid": "Deny non-HTTPS access", "Effect": "Deny", "Principal": "*", "Action": "s3:*", "Resource": "arn:aws:s3:::myBucketName/*", "Condition": { "Bool": { "aws:SecureTransport": "false" } } } ] }
GuardDutyを有効化するCloudFormationテンプレート
以下の記事の内容を参考にします。
記事の主題は全リージョンで有効化するところまでですが、1つのリージョンで有効化する際のテンプレートが載っているので、そこにアドオンする形で作成していきます。
折り畳み
AWSTemplateFormatVersion: 2010-09-09 Description: "enable guardduty and set alert" Parameters: MailAddress: Description: Enter email address to send notification. Type: String Resources: GDD: Type: "AWS::GuardDuty::Detector" Properties: Enable: true SNST: Type: "AWS::SNS::Topic" Properties: TopicName: GuardDutyTopic SNSS: Type: "AWS::SNS::Subscription" Properties: Endpoint: !Ref MailAddress Protocol: email TopicArn: !Ref SNST SNSTP: Type: "AWS::SNS::TopicPolicy" Properties: PolicyDocument: Id: default_policy_ID Version: "2012-10-17" Statement: - Sid: default_statement_ID Effect: Allow Principal: AWS: "*" Action: - "SNS:GetTopicAttributes" - "SNS:SetTopicAttributes" - "SNS:AddPermission" - "SNS:RemovePermission" - "SNS:DeleteTopic" - "SNS:Subscribe" - "SNS:ListSubscriptionsByTopic" - "SNS:Publish" - "SNS:Receive" Resource: !Ref SNST Condition: StringEquals: "AWS:SourceOwner": !Ref "AWS::AccountId" - Sid: AWSEvents_AlertGuardDutyFindings_Id123 Effect: Allow Principal: Service: - "events.amazonaws.com" Action: "sns:Publish" Resource: !Ref SNST Topics: - !Ref SNST ER: Type: "AWS::Events::Rule" Properties: Name: AlertGuardDutyFindings Description: "Alert to SNS topic when find threats by GuardDuty" EventPattern: { "source": [ "aws.guardduty" ], "detail-type": [ "GuardDuty Finding" ] } Targets: - Arn: !Ref SNST Id: Id123
追加する前の時点で、以下のリソースが定義されています。
論理ID | リソースタイプ |
---|---|
GDD | AWS::GuardDuty::Detector |
SNST | AWS::SNS::Topic |
SNSS | AWS::SNS::Subscription |
SNSTP | AWS::SNS::TopicPolicy |
追加するCloudFormationテンプレート
以下をアドオンすることにしました。
折り畳み
# ------------------------------------------------------------# # KMS key for GuardDuty # ------------------------------------------------------------# KMSK: Type: AWS::KMS::Key Properties: Description: "Key for GuardDuty" EnableKeyRotation: true KeyPolicy: Version: "2012-10-17" Id: "key-default-1" Statement: - Sid: "Enable IAM User Permissions" Effect: "Allow" Principal: AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root" Action: "kms:*" Resource: "*" - Sid: "Allow GuardDuty to use the key" Effect: "Allow" Principal: Service: "guardduty.amazonaws.com" Action: - "kms:GenerateDataKey" Resource: "*" PendingWindowInDays: 7 KMSA: Type: AWS::KMS::Alias Properties: AliasName: "alias/KMS-GuardDuty" TargetKeyId: Ref: KMSK # ------------------------------------------------------------# # S3 Bucket for GuardDuty # ------------------------------------------------------------# S3B: Type: "AWS::S3::Bucket" Properties: BucketName: ## バケット名を指定してください ## VersioningConfiguration: Status: Enabled LifecycleConfiguration: Rules: - Id: 365days-All-LifeCycleRule Status: Enabled ExpirationInDays: 365 NoncurrentVersionExpirationInDays: 10 AccessControl: Private PublicAccessBlockConfiguration: BlockPublicAcls: True BlockPublicPolicy: True IgnorePublicAcls: True RestrictPublicBuckets: True S3BP: Type: "AWS::S3::BucketPolicy" Properties: Bucket: Ref: "S3B" PolicyDocument: Version: 2012-10-17 Statement: - Sid: "Deny non-HTTPS access" Action: ['s3:*'] Effect: "Deny" Resource: - !Join ['', ['arn:aws:s3:::', !Ref 'S3B','/*']] Principal: Service: [guardduty.amazonaws.com] Condition: Bool: aws:SecureTransport: "false" - Sid: "Deny incorrect encryption header" Action: ['s3:PutObject'] Effect: "Deny" Resource: - !Join ['', ['arn:aws:s3:::', !Ref 'S3B','/*']] Principal: Service: [guardduty.amazonaws.com] Condition: StringNotEquals: s3:x-amz-server-side-encryption-aws-kms-key-id: !GetAtt KMSK.Arn - Sid: "Deny unencrypted object uploads" Action: ['s3:PutObject'] Effect: "Deny" Resource: - !Join ['', ['arn:aws:s3:::', !Ref 'S3B','/*']] Principal: Service: [guardduty.amazonaws.com] Condition: StringNotEquals: s3:x-amz-server-side-encryption: "aws:kms" - Sid: "Allow PutObject" Action: ['s3:PutObject'] Effect: "Allow" Resource: - !Join ['', ['arn:aws:s3:::', !Ref 'S3B','/*']] Principal: Service: [guardduty.amazonaws.com] - Sid: "Allow GetBucketLocation" Action: ['s3:GetBucketLocation'] Effect: "Allow" Resource: - !Join ['', ['arn:aws:s3:::', !Ref 'S3B']] Principal: Service: [guardduty.amazonaws.com]
追加したリソースは以下の通りです。
論理ID | リソースタイプ |
---|---|
KMSK | AWS::KMS::Key |
KMSA | AWS::KMS::Alias |
S3B | AWS::S3::Bucket |
S3BP | AWS::S3::BucketPolicy |
以下、リソースを順番に確認していきます。
AWS::KMS::Key
KMSK: Type: AWS::KMS::Key Properties: Description: "Key for GuardDuty" EnableKeyRotation: true KeyPolicy: Version: "2012-10-17" Id: "key-default-1" Statement: - Sid: "Enable IAM User Permissions" Effect: "Allow" Principal: AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root" Action: "kms:*" Resource: "*" - Sid: "Allow GuardDuty to use the key" Effect: "Allow" Principal: Service: "guardduty.amazonaws.com" Action: - "kms:GenerateDataKey" Resource: "*" PendingWindowInDays: 7
キーポリシーとしては、デフォルトのポリシー + GuardDutyの要件を満たすように設定しています。
AWS::KMS::Alias
KMSA: Type: AWS::KMS::Alias Properties: AliasName: "alias/KMS-GuardDuty" TargetKeyId: Ref: KMSK
エイリアスがリソースとして分かれているのが少し意外でしたが、キーと1対1ではないことが理由のようです。
各エイリアスは 1 つの CMK のみを指し示すことができますが、複数のエイリアスが同じ CMK を指すことができます。
また、先頭に「alias/
」を付与しないとCloudFormation実行時にエラーになるので注意が必要です。
各エイリアス名は、alias/ で始まり、その後に名前が続きます (alias/exampleKey など)。エイリアス名に使用できるのは、英数字、スラッシュ (/)、アンダースコア (_)、およびダッシュ (-) のみです。エイリアス名を alias/aws/ で始めることはできません。
AWS::S3::Bucket
S3B: Type: "AWS::S3::Bucket" Properties: BucketName: ## バケット名を指定してください ## VersioningConfiguration: Status: Enabled LifecycleConfiguration: Rules: - Id: 365days-All-LifeCycleRule Status: Enabled ExpirationInDays: 365 NoncurrentVersionExpirationInDays: 10 AccessControl: Private PublicAccessBlockConfiguration: BlockPublicAcls: True BlockPublicPolicy: True IgnorePublicAcls: True RestrictPublicBuckets: True
このサンプルではバージョニングを有効化してライフサイクルルールを設定していますが、エクスポートの要件として必要なわけではありません。必要に応じてカスタマイズしてください。 また、S3バケット名は埋め込みで指定するか、パラメータで渡す形にしてください。
AWS::S3::BucketPolicy
S3BP: Type: "AWS::S3::BucketPolicy" Properties: Bucket: Ref: "S3B" PolicyDocument: Version: 2012-10-17 Statement: - Sid: "Deny non-HTTPS access" Action: ['s3:*'] Effect: "Deny" Resource: - !Join ['', ['arn:aws:s3:::', !Ref 'S3B','/*']] Principal: Service: [guardduty.amazonaws.com] Condition: Bool: aws:SecureTransport: "false" - Sid: "Deny incorrect encryption header" Action: ['s3:PutObject'] Effect: "Deny" Resource: - !Join ['', ['arn:aws:s3:::', !Ref 'S3B','/*']] Principal: Service: [guardduty.amazonaws.com] Condition: StringNotEquals: s3:x-amz-server-side-encryption-aws-kms-key-id: !GetAtt KMSK.Arn - Sid: "Deny unencrypted object uploads" Action: ['s3:PutObject'] Effect: "Deny" Resource: - !Join ['', ['arn:aws:s3:::', !Ref 'S3B','/*']] Principal: Service: [guardduty.amazonaws.com] Condition: StringNotEquals: s3:x-amz-server-side-encryption: "aws:kms" - Sid: "Allow PutObject" Action: ['s3:PutObject'] Effect: "Allow" Resource: - !Join ['', ['arn:aws:s3:::', !Ref 'S3B','/*']] Principal: Service: [guardduty.amazonaws.com] - Sid: "Allow GetBucketLocation" Action: ['s3:GetBucketLocation'] Effect: "Allow" Resource: - !Join ['', ['arn:aws:s3:::', !Ref 'S3B']] Principal: Service: [guardduty.amazonaws.com]
上で確認したサンプルのバケットポリシーを再現しています。サンプルではリソースとしてプレフィックス([optional prefix]
)まで絞った形で記載されていますが、このコードではそこまで制限はしていません。
S3へのエクスポート設定
上記を踏まえた形でCloudFormationを実行すればKMSキーとS3バケットが作成されるので、エクスポートの設定をマネジメントコンソールから実施します。
GuardDutyのコンソール画面に遷移し、[設定]ペインを選択します。S3バケットについて「今すぐ設定する」を押下します。
作成したS3バケットとKMSキーを指定して「保存」を押下すれば設定完了です。オプションでプレフィックスを指定することもできます。
終わりに
GuardDutyの検出結果をエクスポートするためのS3バケットとKMSキーを作成するCloudFormationテンプレートのご紹介でした。
あとはエクスポートの設定まで含めてCloudFormation化されることを期待するばかりです。
実際の構成としては複数リージョンの結果を1つのS3バケットに集約したりなど様々なパターンが考えられますが、「リージョンごとにGuardDuty + KMSキー + S3バケット」というユースケースに合致していれば参考にしていただければと思います。
以上、最近バナナをもりもり食べる千葉(幸)がお送りしました。